// 版权所有2011 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package poll

import (
	"io"
	"syscall"
)

// SendFile包装TransmitFile调用。
func SendFile(fd *FD, src syscall.Handle, n int64) (written int64, err error) {
	if fd.kind == kindPipe {
		// TransmitFile不适用于管道
		return 0, syscall.ESPIPE
	}

	if err := fd.writeLock(); err != nil {
		return 0, err
	}
	defer fd.writeUnlock()

	o := &fd.wop
	o.handle = src

	// TODO（brainman）：跳过调用syscall。Seek如果操作系统允许的话
	curpos, err := syscall.Seek(o.handle, 0, io.SeekCurrent)
	if err != nil {
		return 0, err
	}

	if n <= 0 { // 我们不知道文件的大小，所以可以推断出来。
		// 查找从curpos到文件结尾的偏移字节数。
		n, err = syscall.Seek(o.handle, -curpos, io.SeekEnd)
		if err != nil {
			return
		}
		// 现在回到原来的位置。
		if _, err = syscall.Seek(o.handle, curpos, io.SeekStart); err != nil {
			return
		}
	}

	// TransmitFile可以在一次调用中调用，最多使用
	// 2147483646字节：32位整数的最大值减去1。
	// 请参阅https:
	const maxChunkSizePerCall = int64(0x7fffffff - 1)

	for n > 0 {
		chunkSize := maxChunkSizePerCall
		if chunkSize > n {
			chunkSize = n
		}

		o.qty = uint32(chunkSize)
		o.o.Offset = uint32(curpos)
		o.o.OffsetHigh = uint32(curpos >> 32)

		nw, err := execIO(o, func(o *operation) error {
			return syscall.TransmitFile(o.fd.Sysfd, o.handle, o.qty, 0, &o.o, nil, syscall.TF_WRITE_BEHIND)
		})
		if err != nil {
			return written, err
		}

		curpos += int64(nw)

		// 某些版本的Windows（Windows 10 1803）在传输文件完成后未设置
		// 文件位置。
		// 所以只需使用Seek来设置文件位置。
		if _, err = syscall.Seek(o.handle, curpos, io.SeekStart); err != nil {
			return written, err
		}

		n -= int64(nw)
		written += int64(nw)
	}

	return
}
